﻿--Inport: PMEngine, PrivateDB, ProfileDB, GlobalDB
local PMEngine, L, PrivateDB, ProfileDB, GlobalDB = unpack(select(2, ...)); 
local CB = PMEngine:NewModule('CastBars')

PMEngine.CB = CB

local media = LibStub("LibSharedMedia-3.0")
local lsmlist = AceGUIWidgetLSMlists

----------------------------
-- Upvalues
local min, type, format, unpack, setmetatable = math.min, type, string.format, unpack, setmetatable
local CreateFrame, GetTime, UIParent = CreateFrame, GetTime, UIParent
local UnitName, UnitCastingInfo, UnitChannelInfo = UnitName, UnitCastingInfo, UnitChannelInfo

local CastBarTemplate = CreateFrame("Frame")
local CastBarTemplate_MT = {__index = CastBarTemplate}

CB.Util = {}
function CB.Util.TimeFormat(num, isCastTime)
	if num <= 10 or (isCastTime and num <= 60) then
		return ("%%.%df"):format(1), num
	elseif num <= 60 then
		return "%d", num
	elseif num <= 3600 then
		return "%d:%02d", num / 60, num % 60
	else
		return "%d:%02d", num / 3600, (num % 3600) / 60
	end
end
local TimeFmt = CB.Util.TimeFormat

local playerName = UnitName("player")

local function call(obj, method, ...)
	if type(obj.parent[method]) == "function" then
		return obj.parent[method](obj.parent, obj, ...)
	end
end

----------------------------
-- Frame Scripts

-- OnShow and OnHide are not used by the template
-- But forward the call to the embeding module, they might use it.
local function OnShow(self)
	call(self, "OnShow")
end

local function OnHide(self)
	call(self, "OnHide")
end

-- OnUpdate handles the bar movement and the text updates
local function OnUpdate(self)
	local currentTime = GetTime()
	local startTime, endTime, delay = self.startTime, self.endTime, self.delay
	local db = self.config
	if self.channeling or self.casting then
		local perc, remainingTime, delayFormat, delayFormatTime
		if self.casting then
			local showTime = min(currentTime, endTime)
			remainingTime = endTime - showTime
			perc = (showTime - startTime) / (endTime - startTime)

			delayFormat, delayFormatTime = "|cffff0000+%.1f|cffffffff %s", "|cffff0000+%.1f|cffffffff %s / %s"
		elseif self.channeling then
			remainingTime = endTime - currentTime
			perc = remainingTime / (endTime - startTime)
			
			delayFormat, delayFormatTime = "|cffff0000-%.1f|cffffffff %s", "|cffff0000-%.1f|cffffffff %s / %s"
		end

		self.Bar:SetValue(perc)
		self.Spark:ClearAllPoints()
		self.Spark:SetPoint("CENTER", self.Bar, "LEFT", perc * db.w, 0)

		if delay and delay ~= 0 then
			if db.hidecasttime then
				self.TimeText:SetFormattedText("|cffff0000+%.1f|cffffffff %s", delay, format(TimeFmt(remainingTime)))
			else
				self.TimeText:SetFormattedText("|cffff0000+%.1f|cffffffff %s / %s", delay, format(TimeFmt(remainingTime)), format(TimeFmt(endTime - startTime, true)))
			end
		else
			if db.hidecasttime then
				self.TimeText:SetFormattedText(TimeFmt(remainingTime))
			else
				self.TimeText:SetFormattedText("%s / %s", format(TimeFmt(remainingTime)), format(TimeFmt(endTime - startTime, true)))
			end
		end

		if currentTime > endTime then
			self.casting, self.channeling = nil, nil
			self.fadeOut = true
			self.stopTime = currentTime
		end
	elseif self.fadeOut then
		self.Spark:Hide()
		local alpha
		local stopTime = self.stopTime
		if stopTime then
			alpha = stopTime - currentTime + 1
		else
			alpha = 0
		end
		if alpha >= 1 then
			alpha = 1
		end
		if alpha <= 0 then
			self.stopTime = nil
			self:Hide()
		else
			self:SetAlpha(alpha*db.alpha)
		end
	else
		self:Hide()
	end
end
CastBarTemplate.OnUpdate = OnUpdate

local function OnEvent(self, event, ...)
	if self[event] then
		self[event](self, event, ...)
	end
end

----------------------------
-- Template Methods

local function SetNameText(self, name)
	if --[[self.config.targetname and]] self.targetName and self.targetName ~= "" then
		self.Text:SetFormattedText("%s -> %s", name, self.targetName)
	else
		self.Text:SetText(name)
	end
end
CastBarTemplate.SetNameText = SetNameText

local function ToggleCastNotInterruptible(self, notInterruptible, init)
	if self.unit == "player" and not init then return end
	--local db = self.config

	if notInterruptible then
		self.Bar:SetStatusBarColor(1.0, 0.49, 0)
	end

	local r, g, b, a
	if notInterruptible then
		self.backdrop.edgeFile = media:Fetch("border", "Tooltip enlarged")
		r,g,b = 0.71, 0.73, 0.71
		a = 1
	else
		self.backdrop.edgeFile = media:Fetch("border", "Blizzard Tooltip")
		r,g,b = 0, 0, 0
		a = 1
	end
	self:SetBackdrop(self.backdrop)
	self:SetBackdropBorderColor(r, g, b, a)

	r, g, b = 0, 0, 0
	self:SetBackdropColor(r, g, b, 1)

	if self.Shield then
		if notInterruptible then
			self.Shield:Show()
		else
			self.Shield:Hide()
		end
	end

	self.lastNotInterruptible = notInterruptible
end
CastBarTemplate.ToggleCastNotInterruptible = ToggleCastNotInterruptible

----------------------------
-- Event Handlers

function CastBarTemplate:UNIT_SPELLCAST_SENT(event, unit, spell, rank, target)
	if unit ~= self.unit and not (self.unit == "player" and unit == "vehicle") then
		return
	end
	if target then
		self.targetName = target
	else
		-- auto selfcast? is this needed, even?
		self.targetName = playerName
	end

	call(self, "UNIT_SPELLCAST_SENT", unit, spell, rank, target)
end

function CastBarTemplate:UNIT_SPELLCAST_START(event, unit)
	if (unit ~= self.unit and not (self.unit == "player" and unit == "vehicle")) or call(self, "PreShowCondition", unit) then
		return
	end
	--local db = self.config
	if event == "UNIT_SPELLCAST_START" then
		self.casting, self.channeling = true, nil
	else
		self.casting, self.channeling = nil, true
	end

	local spell, rank, displayName, icon, startTime, endTime, isTradeSkill, castID, notInterruptible
	if self.casting then
		spell, rank, displayName, icon, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unit)
	else -- self.channeling
		spell, rank, displayName, icon, startTime, endTime, isTradeSkill, notInterruptible = UnitChannelInfo(unit)
		-- channeling spells sometimes just display "Channeling" - this is not wanted
		displayName = spell
	end
	-- in case this returned nothing
	if not startTime or not endTime then return end

	startTime = startTime / 1000
	endTime = endTime / 1000
	self.startTime = startTime
	self.endTime = endTime
	self.delay = 0
	self.fadeOut = nil

	self.Bar:SetStatusBarColor(1.0, 0.49, 0)

	self.Bar:SetValue(self.casting and 0 or 1)
	self:Show()
	self:SetAlpha(1)

	SetNameText(self, displayName)

	self.Spark:Show()

	if icon == "Interface\\Icons\\Temp" then
		icon = nil
	end
	self.Icon:SetTexture(icon)

	local position = "right"
	if position == "caststart" or position == "castend" then
		if (position == "caststart" and self.casting) or (position == "castend" and self.channeling) then
			self.TimeText:SetPoint("LEFT", self.Bar, "LEFT", 3, 0)
			self.TimeText:SetJustifyH("LEFT")
		else
			self.TimeText:SetPoint("RIGHT", self.Bar, "RIGHT", -1 * 3, 0)
			self.TimeText:SetJustifyH("RIGHT")
		end
	end

	ToggleCastNotInterruptible(self, notInterruptible)

	call(self, "UNIT_SPELLCAST_START", unit)
end
CastBarTemplate.UNIT_SPELLCAST_CHANNEL_START = CastBarTemplate.UNIT_SPELLCAST_START

function CastBarTemplate:UNIT_SPELLCAST_STOP(event, unit)
	if not (self.channeling or self.casting) or (unit ~= self.unit and not (self.unit == "player" and unit == "vehicle")) then
		return
	end

	self.Bar:SetValue(self.casting and 1.0 or 0)
	self.Bar:SetStatusBarColor(0.12, 0.86, 0.15)

	self.casting, self.channeling = nil, nil
	self.fadeOut = true
	self.stopTime = GetTime()

	self.TimeText:SetText("")

	call(self, "UNIT_SPELLCAST_STOP", unit)
end
CastBarTemplate.UNIT_SPELLCAST_CHANNEL_STOP = CastBarTemplate.UNIT_SPELLCAST_STOP

function CastBarTemplate:UNIT_SPELLCAST_FAILED(event, unit)
	if self.channeling or self.casting or (unit ~= self.unit and not (self.unit == "player" and unit == "vehicle")) then
		return
	end
	self.fadeOut = true
	if not self.stopTime then
		self.stopTime = GetTime()
	end
	self.Bar:SetValue(1.0)
	self.Bar:SetStatusBarColor(1.0, 0.09, 0)

	self.TimeText:SetText("")

	call(self, "UNIT_SPELLCAST_FAILED", unit)
end

function CastBarTemplate:UNIT_SPELLCAST_INTERRUPTED(event, unit)
	if unit ~= self.unit and not (self.unit == "player" and unit == "vehicle") then
		return
	end
	self.casting, self.channeling = nil, nil
	self.fadeOut = true
	if not self.stopTime then
		self.stopTime = GetTime()
	end
	self.Bar:SetValue(1.0)
	self.Bar:SetStatusBarColor(1.0, 0.09, 0)

	self.TimeText:SetText("")

	call(self, "UNIT_SPELLCAST_INTERRUPTED", unit)
end
CastBarTemplate.UNIT_SPELLCAST_CHANNEL_INTERRUPTED = CastBarTemplate.UNIT_SPELLCAST_INTERRUPTED

function CastBarTemplate:UNIT_SPELLCAST_DELAYED(event, unit)
	if unit ~= self.unit and not (self.unit == "player" and unit == "vehicle") or call(self, "PreShowCondition", unit) then
		return
	end
	local oldStart = self.startTime
	local spell, rank, displayName, icon, startTime, endTime
	if self.casting then
		spell, rank, displayName, icon, startTime, endTime = UnitCastingInfo(unit)
	else
		spell, rank, displayName, icon, startTime, endTime = UnitChannelInfo(unit)
	end

	if not startTime or not endTime then
		return self:Hide()
	end

	startTime = startTime / 1000
	endTime = endTime / 1000
	self.startTime = startTime
	self.endTime = endTime

	if self.casting then
		self.delay = (self.delay or 0) + (startTime - (oldStart or startTime))
	else
		self.delay = (self.delay or 0) + ((oldStart or startTime) - startTime)
	end

	call(self, "UNIT_SPELLCAST_DELAYED", unit)
end
CastBarTemplate.UNIT_SPELLCAST_CHANNEL_UPDATE = CastBarTemplate.UNIT_SPELLCAST_DELAYED


function CastBarTemplate:UNIT_SPELLCAST_INTERRUPTIBLE(event, unit)
	if unit ~= self.unit then
		return
	end
	ToggleCastNotInterruptible(self, false)
end

function CastBarTemplate:UNIT_SPELLCAST_NOT_INTERRUPTIBLE(event, unit)
	if unit ~= self.unit then
		return
	end
	ToggleCastNotInterruptible(self, true)
end

function CastBarTemplate:UpdateUnit()
	if UnitCastingInfo(self.unit) then
		self:UNIT_SPELLCAST_START("UNIT_SPELLCAST_START", self.unit)
	elseif UnitChannelInfo(self.unit) then
		self:UNIT_SPELLCAST_START("UNIT_SPELLCAST_CHANNEL_START", self.unit)
	else
		self:Hide()
	end
end

--function CastBarTemplate:ApplySettings()
--	local db = self.config

--	self:ClearAllPoints()
--	if not db.x then
--		db.x = (UIParent:GetWidth() / 2 - (db.w * db.scale) / 2) / db.scale
--	end
--	self:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", db.x, db.y)
--	self:SetWidth(db.w + 10)
--	self:SetHeight(db.h + 10)
--	self:SetAlpha(db.alpha)
--	self:SetScale(db.scale)

--	ToggleCastNotInterruptible(self, self.lastNotInterruptible, true)

--	self.Bar:ClearAllPoints()
--	self.Bar:SetPoint("CENTER",self,"CENTER")
--	self.Bar:SetWidth(db.w)
--	self.Bar:SetHeight(db.h)
--	self.Bar:SetStatusBarTexture(media:Fetch("statusbar", db.texture))
--	self.Bar:SetMinMaxValues(0, 1)

--	if db.hidetimetext then
--		self.TimeText:Hide()
--	else
--		self.TimeText:Show()
--		self.TimeText:ClearAllPoints()
--		self.TimeText:SetWidth(db.w)
--		local position = db.timetextposition
--		if position == "left" then
--			self.TimeText:SetPoint("LEFT", self.Bar, "LEFT", db.timetextx, db.timetexty)
--			self.TimeText:SetJustifyH("LEFT")
--		elseif position == "center" then
--			self.TimeText:SetPoint("CENTER", self.Bar, "CENTER", db.timetextx, db.timetexty)
--			self.TimeText:SetJustifyH("CENTER")
--		elseif position == "right" then
--			self.TimeText:SetPoint("RIGHT", self.Bar, "RIGHT", -1 * db.timetextx, db.timetexty)
--			self.TimeText:SetJustifyH("RIGHT")
--		end -- "Cast Start Side", "Cast End Side" -- handled at runtime
--	end
--	self.TimeText:SetFont(media:Fetch("font", db.font), db.timefontsize)
--	self.TimeText:SetShadowColor( 0, 0, 0, 1)
--	self.TimeText:SetShadowOffset( 0.8, -0.8 )
--	self.TimeText:SetTextColor(unpack(Quartz3.db.profile.timetextcolor))
--	self.TimeText:SetNonSpaceWrap(false)
--	self.TimeText:SetHeight(db.h)

--	local temptext = self.TimeText:GetText()
--	if db.hidecasttime then
--		self.TimeText:SetFormattedText(TimeFmt(10))
--	else
--		self.TimeText:SetFormattedText("%s / %s", format(TimeFmt(10)), format(TimeFmt(10, true)))
--	end
--	local normaltimewidth = self.TimeText:GetStringWidth()
--	self.TimeText:SetText(temptext)

--	if db.hidenametext then
--		self.Text:Hide()
--	else
--		self.Text:Show()
--		self.Text:ClearAllPoints()
--		local position = db.nametextposition
--		if position == "left" then
--			self.Text:SetPoint("LEFT", self.Bar, "LEFT", db.nametextx, db.nametexty)
--			self.Text:SetJustifyH("LEFT")
--			if db.hidetimetext or db.timetextposition ~= "right" then
--				self.Text:SetWidth(db.w)
--			else
--				self.Text:SetWidth(db.w - normaltimewidth - 5)
--			end
--		elseif position == "center" then
--			self.Text:SetPoint("CENTER", self.Bar, "CENTER", db.nametextx, db.nametexty)
--			self.Text:SetJustifyH("CENTER")
--		else -- "Right"
--			self.Text:SetPoint("RIGHT", self.Bar, "RIGHT", -1 * db.nametextx, db.nametexty)
--			self.Text:SetJustifyH("RIGHT")
--			if db.hidetimetext or db.timetextposition ~= "left" then
--				self.Text:SetWidth(db.w)
--			else
--				self.Text:SetWidth(db.w - normaltimewidth - 5)
--			end
--		end
--	end
--	self.Text:SetFont(media:Fetch("font", db.font), db.fontsize)
--	self.Text:SetShadowColor( 0, 0, 0, 1)
--	self.Text:SetShadowOffset( 0.8, -0.8 )
--	self.Text:SetTextColor(unpack(Quartz3.db.profile.spelltextcolor))
--	self.Text:SetNonSpaceWrap(false)
--	self.Text:SetHeight(db.h)

--	if db.hideicon then
--		self.Icon:Hide()
--	else
--		self.Icon:Show()
--		self.Icon:ClearAllPoints()
--		if db.iconposition == "left" then
--			self.Icon:SetPoint("RIGHT", self.Bar, "LEFT", -1 * db.icongap, 0)
--		else --"Right"
--			self.Icon:SetPoint("LEFT", self.Bar, "RIGHT", db.icongap, 0)
--		end
--		self.Icon:SetWidth(db.h)
--		self.Icon:SetHeight(db.h)
--		self.Icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
--		self.Icon:SetAlpha(db.iconalpha)
--	end

--	self.Spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
--	self.Spark:SetVertexColor(unpack(Quartz3.db.profile.sparkcolor))
--	self.Spark:SetBlendMode("ADD")
--	self.Spark:SetWidth(20)
--	self.Spark:SetHeight(db.h*2.2)
--end

function CastBarTemplate:RegisterEvents()
	if self.unit == "player" then
		self:RegisterEvent("UNIT_SPELLCAST_SENT")
	end
	self:RegisterEvent("UNIT_SPELLCAST_START")
	self:RegisterEvent("UNIT_SPELLCAST_STOP")
	self:RegisterEvent("UNIT_SPELLCAST_FAILED")
	self:RegisterEvent("UNIT_SPELLCAST_DELAYED")
	self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
	self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
	self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_UPDATE")
	self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
	self:RegisterEvent("UNIT_SPELLCAST_CHANNEL_INTERRUPTED")
	if self.unit ~= "player" then
		self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTIBLE")
		self:RegisterEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE")
	end

	media.RegisterCallback(self, "LibSharedMedia_SetGlobal", function(mtype, override)
		if mtype == "statusbar" then
			self.Bar:SetStatusBarTexture(media:Fetch("statusbar", override))
		end
	end)

	media.RegisterCallback(self, "LibSharedMedia_Registered", function(mtype, key)
		if mtype == "statusbar" and key == self.config.texture then
			self.Bar:SetStatusBarTexture(media:Fetch("statusbar", self.config.texture))
		end
	end)
end

function CastBarTemplate:UnregisterEvents()
	self:UnregisterAllEvents()
	media.UnregisterCallback(self, "LibSharedMedia_SetGlobal")
	media.UnregisterCallback(self, "LibSharedMedia_Registered")
end

do
	local function dragstart(self)
		self:StartMoving()
	end

	local function dragstop(self)
		self.config.x = self:GetLeft()
		self.config.y = self:GetBottom()
		self:StopMovingOrSizing()
	end

	local function nothing(self)
		self:SetAlpha(self.config.alpha)
	end

	function CastBarTemplate:Unlock()
		self:Show()
		self:EnableMouse(true)
		self:SetScript("OnDragStart", dragstart)
		self:SetScript("OnDragStop", dragstop)
		self:SetAlpha(1)
		self.Hide = nothing
		self.Icon:SetTexture("Interface\\Icons\\Temp")
		self.Text:SetText(self.unit)
	end

	function CastBarTemplate:Lock()
		self.Hide = nil
		self:EnableMouse(false)
		self:SetScript("OnDragStart", nil)
		self:SetScript("OnDragStop", nil)
		if not (self.channeling or self.casting) then
			self:Hide()
		end
	end
end

CB.CastBarTemplate = {}
CB.CastBarTemplate.defaults = {
	--x =  -- applied automatically in applySettings()
	y = 180,
	h = 25,
	w = 250,
	scale = 1,
	texture = "Blizzard",
	hideicon = false,
	alpha = 1,
	iconalpha = 0.9,
	iconposition = "left",
	icongap = 4,
	hidenametext = false,
	nametextposition = "left",
	timetextposition = "right",
	font = "Friz Quadrata TT",
	fontsize = 14,
	hidetimetext = false,
	hidecasttime = false,
	timefontsize = 12,
	targetname = false,
	border = "Blizzard Tooltip",
	nametextx = 3,
	nametexty = 0,
	timetextx = 3,
	timetexty = 0,

	noInterruptBorderChange = false,
	noInterruptBorder = "Tooltip enlarged",
	noInterruptBorderColor = {0.71, 0.73, 0.71}, -- Default color chosen by playing around with settings, rounded to 2 significant digits
	noInterruptBorderAlpha = 1,
	noInterruptColorChange = false,
	noInterruptColor = {1.0, 0.49, 0},
	noInterruptShield = true,
}
CB.CastBarTemplate.template = CastBarTemplate
CB.CastBarTemplate.bars = {}
function CB.CastBarTemplate:new(parent, unit, name, localizedName, config)
	local frameName = "Quartz3CastBar" .. name
	local bar = setmetatable(CreateFrame("Frame", frameName, UIParent), CastBarTemplate_MT)
	bar.unit = unit
	bar.parent = parent
	bar.config = config
	bar.modName = name
	bar.localizedName = localizedName
	bar.locked = true

	CB.CastBarTemplate.bars[name] = bar

	bar:SetFrameStrata("MEDIUM")
	bar:SetScript("OnShow", OnShow)
	bar:SetScript("OnHide", OnHide)
	bar:SetScript("OnUpdate", OnUpdate)
	bar:SetScript("OnEvent", OnEvent)
	bar:SetMovable(true)
	bar:RegisterForDrag("LeftButton")
	bar:SetClampedToScreen(true)

	bar.Bar      = CB:CreateStatusBar(nil, bar) --CreateFrame("StatusBar", nil, bar)
	bar.Text     = bar.Bar:CreateFontString(nil, "OVERLAY")
	bar.TimeText = bar.Bar:CreateFontString(nil, "OVERLAY")
	bar.Icon     = bar.Bar:CreateTexture(nil, "DIALOG")
	bar.Spark    = bar.Bar:CreateTexture(nil, "OVERLAY")
	if unit ~= "player" then
		bar.Shield = bar.Bar:CreateTexture(nil, "ARTWORK")
		bar.Shield:SetTexture("Interface\\CastingBar\\UI-CastingBar-Small-Shield")
		bar.Shield:SetTexCoord(0, 36/256, 0, 1)
		bar.Shield:SetWidth(36)
		bar.Shield:SetHeight(64)
		bar.Shield:SetPoint("CENTER", bar.Icon, "CENTER", -2, -1)
		bar.Shield:Hide()
	end

	bar.lastNotInterruptible = false

	bar.backdrop = { bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
	                 tile = true, tileSize = 16, edgeSize = 16, --edgeFile = "", -- set by ApplySettings
	                 insets = {left = 4, right = 4, top = 4, bottom = 4} }
	bar:Hide()

	return bar
end